/*
 * Copyright 2016-present the IoT DC3 original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package io.github.pnoker.common.driver.service.impl;

import io.github.pnoker.common.constant.driver.RabbitConstant;
import io.github.pnoker.common.driver.entity.bean.PointValue;
import io.github.pnoker.common.driver.entity.property.DriverProperties;
import io.github.pnoker.common.driver.service.DriverSenderService;
import io.github.pnoker.common.entity.dto.DeviceEventDTO;
import io.github.pnoker.common.entity.dto.DriverEventDTO;
import io.github.pnoker.common.enums.DeviceEventTypeEnum;
import io.github.pnoker.common.enums.DeviceStatusEnum;
import io.github.pnoker.common.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Implementation of the {@code DriverSenderService} interface.
 * Provides methods to send various events and data related to drivers and devices to a message queue.
 */
@Slf4j
@Service
public class DriverSenderServiceImpl implements DriverSenderService {

    private final DriverProperties driverProperties;
    private final RabbitTemplate rabbitTemplate;

    public DriverSenderServiceImpl(DriverProperties driverProperties, RabbitTemplate rabbitTemplate) {
        this.driverProperties = driverProperties;
        this.rabbitTemplate = rabbitTemplate;
    }

    /**
     * Send driver event to message queue
     *
     * @param entityDTO Driver event data transfer object
     */
    @Override
    public void driverEventSender(DriverEventDTO entityDTO) {
        if (Objects.isNull(entityDTO)) {
            return;
        }

        rabbitTemplate.convertAndSend(
                RabbitConstant.TOPIC_EXCHANGE_EVENT,
                RabbitConstant.ROUTING_DRIVER_EVENT_PREFIX + driverProperties.getService(),
                entityDTO
        );
    }

    /**
     * Send device event to message queue
     *
     * @param entityDTO Device event data transfer object
     */
    @Override
    public void deviceEventSender(DeviceEventDTO entityDTO) {
        if (!Objects.nonNull(entityDTO)) {
            return;
        }

        rabbitTemplate.convertAndSend(
                RabbitConstant.TOPIC_EXCHANGE_EVENT,
                RabbitConstant.ROUTING_DEVICE_EVENT_PREFIX + driverProperties.getService(),
                entityDTO
        );
    }

    /**
     * Send device status with default timeout (15 minutes)
     *
     * @param deviceId Device ID
     * @param status   Device status enum
     */
    @Override
    public void deviceStatusSender(Long deviceId, DeviceStatusEnum status) {
        sendDeviceStatus(deviceId, status, 15, TimeUnit.MINUTES);
    }

    /**
     * Send device status with custom timeout
     *
     * @param deviceId Device ID
     * @param status   Device status enum
     * @param timeOut  Timeout value
     * @param timeUnit Time unit for timeout
     */
    @Override
    public void deviceStatusSender(Long deviceId, DeviceStatusEnum status, int timeOut, TimeUnit timeUnit) {
        sendDeviceStatus(deviceId, status, timeOut, timeUnit);
    }

    /**
     * Send single point value to message queue
     *
     * @param entityDTO Point value data transfer object
     */
    @Override
    public void pointValueSender(PointValue entityDTO) {
        if (Objects.nonNull(entityDTO)) {
            log.info("Send point value: {}", JsonUtil.toJsonString(entityDTO));
            rabbitTemplate.convertAndSend(
                    RabbitConstant.TOPIC_EXCHANGE_VALUE,
                    RabbitConstant.ROUTING_POINT_VALUE_PREFIX + driverProperties.getService(),
                    entityDTO
            );
        }
    }

    /**
     * Send multiple point values to message queue
     *
     * @param entityDTOList List of point value data transfer objects
     */
    @Override
    public void pointValueSender(List<PointValue> entityDTOList) {
        if (Objects.nonNull(entityDTOList)) {
            entityDTOList.forEach(this::pointValueSender);
        }
    }

    /**
     * Helper method to send device status event
     *
     * @param deviceId Device ID
     * @param status   Device status enum
     * @param timeOut  Timeout value
     * @param timeUnit Time unit for timeout
     */
    private void sendDeviceStatus(Long deviceId, DeviceStatusEnum status, int timeOut, TimeUnit timeUnit) {
        DeviceEventDTO.DeviceStatus deviceStatus = new DeviceEventDTO.DeviceStatus(deviceId, status, timeOut, timeUnit);
        DeviceEventDTO deviceEventDTO = new DeviceEventDTO(DeviceEventTypeEnum.HEARTBEAT, JsonUtil.toJsonString(deviceStatus));
        log.info("Report device event: {}, event content: {}", deviceEventDTO.getType().getCode(), JsonUtil.toJsonString(deviceEventDTO));
        deviceEventSender(deviceEventDTO);
    }

}
